package jmine.tec.datadigester.positional;

import static jmine.tec.datadigester.DataDigesterMessages.MAPEAMENTO_POSICIONAL_NAO_ANOTADO;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.datadigester.positional.annotations.PositionalFileLayout;
import jmine.tec.datadigester.positional.meta.PositionalFieldMetadata;
import jmine.tec.datadigester.positional.meta.PositionalLayoutMetadataFactory;
import jmine.tec.datadigester.positional.writer.PositionalFieldWriter;
import jmine.tec.datadigester.positional.writer.factory.BigDecimalPositionalWriterFactory;
import jmine.tec.datadigester.positional.writer.factory.DatePositionalWriterFactory;
import jmine.tec.datadigester.positional.writer.factory.IntPositionalWriterFactory;
import jmine.tec.datadigester.positional.writer.factory.LongPositionalWriterFactory;
import jmine.tec.datadigester.positional.writer.factory.PositionalWriterFactory;
import jmine.tec.datadigester.positional.writer.factory.StringPositionalWriterFactory;
import jmine.tec.datadigester.positional.writer.factory.TimestampPositionalWriterFactory;
import bancosys.tec.datadigester.DigesterDriverException;

/**
 * Analisa o objeto passado em busca de anotações indicando o mapeamento posicional
 * 
 * @author israel.crisanto
 */
public class PositionalFileFiller {

    private PositionalLayoutMetadataFactory metadataFactory = new PositionalLayoutMetadataFactory();

    private Map<Class<?>, PositionalWriterFactory<?>> writerFactories = new HashMap<Class<?>, PositionalWriterFactory<?>>();

    private Map<Class<?>, List<PositionalFieldWriter>> parsedClasses = new HashMap<Class<?>, List<PositionalFieldWriter>>();

    /**
     * Construtor
     */
    public PositionalFileFiller() {
        this.addWriterFactory(new StringPositionalWriterFactory());
        this.addWriterFactory(new LongPositionalWriterFactory());
        this.addWriterFactory(new BigDecimalPositionalWriterFactory());
        this.addWriterFactory(new TimestampPositionalWriterFactory());
        this.addWriterFactory(new IntPositionalWriterFactory());
        this.addWriterFactory(new DatePositionalWriterFactory());
    }

    /**
     * Adds support to a positional writer factory
     * 
     * @param writerFactory writerFactory
     */
    private void addWriterFactory(PositionalWriterFactory writerFactory) {
        this.writerFactories.put(writerFactory.getFieldMetadataType(), writerFactory);
    }

    /**
     * Extrai da linha informada os valores mapeados no objeto
     * 
     * @param bean objeto com anotações de mapeamento
     * @param line linha com conteúdo a ser extraido
     * @throws DigesterDriverException DigesterDriverException
     */
    public void parse(Object bean, String line) {

        if (!this.classIsAnnotatedAsLayout(bean.getClass())) {
            throw new DigesterDriverException(MAPEAMENTO_POSICIONAL_NAO_ANOTADO.create(bean.getClass().getName()));
        }

        this.loadFieldWritersIfNeeded(bean.getClass());

        for (PositionalFieldWriter writer : this.parsedClasses.get(bean.getClass())) {
            writer.writeToBean(bean, line);
        }

    }

    /**
     * Verifica se a classe foi anotada com {@link PositionalFileLayout}
     * 
     * @param beanClass beanClass
     * @return se a classe está devidamente anotada ou não
     */
    private boolean classIsAnnotatedAsLayout(Class beanClass) {
        if (this.parsedClasses.containsKey(beanClass)) {
            return true;
        }

        Annotation[] annotations = beanClass.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof PositionalFileLayout) {
                return true;
            }
        }

        return false;
    }

    /**
     * Carrega os writers e métodos da classe se for necessário
     * 
     * @param beanClass classe a ser analisada
     */
    private void loadFieldWritersIfNeeded(Class beanClass) {
        if (!this.parsedClasses.containsKey(beanClass)) {
            this.loadFieldWritersForClass(beanClass);
        }
    }

    /**
     * Carrega os writers para uma classe específica
     * 
     * @param beanClass classe a ser analisada
     */
    private void loadFieldWritersForClass(Class beanClass) {
        List<PositionalFieldWriter> writers = new ArrayList<PositionalFieldWriter>();

        for (PositionalFieldMetadata meta : this.metadataFactory.forBean(beanClass).getFields()) {
            if (this.writerFactories.containsKey(meta.getClass())) {
                writers.add(this.createWriter(this.writerFactories.get(meta.getClass()), meta));
            }
        }

        this.parsedClasses.put(beanClass, writers);
    }

    /**
     * @param <M> writer type
     * @param writerFactory writerFactory
     * @param meta meta
     * @return writer
     */
    private <M> PositionalFieldWriter createWriter(PositionalWriterFactory<M> writerFactory, PositionalFieldMetadata meta) {
        return writerFactory.create(writerFactory.getFieldMetadataType().cast(meta));
    }

    /**
     * @return metadataFactory
     */
    public PositionalLayoutMetadataFactory getMetadataFactory() {
        return this.metadataFactory;
    }
}
